/*
** Text-Based Gaming System
** Copyright (C) 2008 Eric Day
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to:
** Free Software Foundation, Inc.
** 51 Franklin Street, Fifth Floor,
** Boston, MA  02110-1301, USA
*/

#include "tgs.h"

/*
** static functions
*/

/* parse and handle user input */
int _user_parse(TGS *tgs, char *buf, int len);

/* parse and handle server input */
int _server_parse(TGS *tgs, char *buf, int len);

/* find a game module given a name */
static TGS_GAME *_game_find(char *name);

/* update the status line */
int _update_status(TGS *tgs, time_t cur_time, char *server, uint16_t port);

/* print usage information */
void _usage(char *name);

/* static variables */
static TGS_GAME *_game_list[] =
{
    &tgs_draw,
    &tgs_sudoku,
    NULL
};

/*
** main function
*/

int main(int argc, char *argv[])
{
    char            c;
    char           *server = TGS_DEFAULT_SERVER;
    uint16_t        port = TGS_DEFAULT_PORT;
    int             ret;
    TGS             tgs;
    struct pollfd   pfd[2];
    struct timeval  tv;
    char            buf[TGS_STR_LEN];
    int             len;

    memset(&tgs, 0, sizeof(TGS));

    while ((c = getopt(argc, argv, "p:s:h")) != EOF)
    {   
        switch(c)
        {
        case 'p':
            port = atoi(optarg);
            break;

        case 's':
            server = optarg;
            break;

        case 'h':
        default:
            _usage(argv[0]);
            return 0;
        }
    }

    /* set any signal handlers */
    TGS_RET_CHECK(&tgs, signal(SIGPIPE, SIG_IGN) == SIG_ERR, "signal", errno);

    /* start connection to server */
    ret = lpc_connect(&(tgs.lpc), &(pfd[1]), server, port);
    TGS_RET_CHECK(&tgs, ret, "lpc_init", ret);

    /* initialize the ui */
    ret = tui_init(&(tgs.tui), &(pfd[0]), TGS_GAME_WIN_HEIGHT);
    TGS_RET_CHECK(&tgs, ret, "tui_init", ret);

    tgs.game_win = tui_get_app_window(tgs.tui);

    tui_set_prompt(tgs.tui, TGS_PROMPT_NAME);

    /* set default game status string */
    ret = tui_set_app_status(tgs.tui, TGS_DEFAULT_GAME_STATUS, 1);
    TGS_RET_CHECK(&tgs, ret, "tui_set_app_status", ret);

    ret = tui_scroll_write(tgs.tui, "*** Type /help For Available Commands");
    TGS_RET_CHECK(&tgs, ret, "tui_scroll_write", ret);

    while (1)
    {
        ret = gettimeofday(&tv, NULL);
        TGS_RET_CHECK(&tgs, ret == -1, "gettimeofday", errno);

        ret = _update_status(&tgs, tv.tv_sec, server, port);
        TGS_RET_CHECK(&tgs, ret, "tui_set_status", ret);

        /* update the ui */
        ret = tui_update(tgs.tui);
        TGS_RET_CHECK(&tgs, ret, "tui_update", ret);

        /* wait for user input or socket activity */
        ret = poll(pfd, 2, 1000);
        TGS_RET_CHECK(&tgs, ret == -1 && errno != EINTR, "poll", errno);

        /* check to see if there is any user input */
        if (pfd[0].revents & POLLIN)
        {
            ret = tui_input(tgs.tui);
            TGS_RET_CHECK(&tgs, ret, "tui_input", ret);

            len = tui_read(tgs.tui, buf, TGS_STR_LEN);
            if (len >= 0)
            {
                ret = _user_parse(&tgs, buf, len);
                TGS_RET_CHECK(&tgs, ret, "_user_parse", ret);
            }
        }

        /* check for socket activity or if we're not connected. only allow
           one connect attempt per second */
        if (pfd[1].revents || (pfd[1].fd == -1 && tgs.last_act != tv.tv_sec))
        {
            tgs.last_act = tv.tv_sec;

            ret = lpc_activity(tgs.lpc);
            TGS_RET_CHECK(&tgs, ret, "lpc_activity", ret);

            while (1)
            {
                len = lpc_read(tgs.lpc, buf, TGS_STR_LEN);

                if (len == -1)
                    break;

                ret = _server_parse(&tgs, buf, len);
                TGS_RET_CHECK(&tgs, ret, "_server_parse", ret);
            }
        }

        if (pfd[1].fd == -1 && tgs.game != NULL)
        {
            ret = tgs.game->part(&tgs);
            TGS_RET_CHECK(&tgs, ret, "game->part", ret);
        }
    }

    tui_destroy(tgs.tui);

    return 0;
}

/*
** global functions
*/

/*
** static functions
*/

/* handle user input */
int _user_parse(TGS *tgs, char *buf, int len)
{
    int   ret;
    char *tok;
    char *a;
    int   x;

    if (tgs->name[0] == 0)
    {
        if (strlen(buf) >= TGS_NAME_LEN)
        {
            ret = tui_scroll_write(tgs->tui, "*** Name Too Long");
            TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
            return 0;
        }

        ret = lpc_printf(tgs->lpc, "NAME %s", buf);
        TGS_RET_CHECK(tgs, ret, "lpc_printf", ret);

        strcpy(tgs->name, buf);
        tui_set_prompt(tgs->tui, TGS_PROMPT);

        return 0;
    }

    if (buf[0] != '/' || (len > 1 && buf[1] == '/'))
    {
        if(!strncmp(buf, "//", 2))
        {   
            memmove(buf, buf + 1, len);
            len--;
        }

        if (strlen(buf) == 0)
            return 0;

        ret = lpc_printf(tgs->lpc, "MSG * %s", buf);
        TGS_RET_CHECK(tgs, ret, "lpc_printf", ret);

        ret = tui_scroll_printf(tgs->tui, "<%s> %s", tgs->name, buf);
        TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);

        return 0;
    }

    tok = strtok(buf, " \t");
    if (tok == NULL)
        return 0;

    if (!strcasecmp(tok, "/exit") || !strcasecmp(tok, "/quit"))
    {
        tui_destroy(tgs->tui);
        exit(0);
    }
    else if (!strcasecmp(tok, "/msg"))
    {
        a = strtok(NULL, " \t");
        TGS_TOK_CHECK(tgs, a, "Invalid Private Message Command");

        tok = strtok(NULL, "");
        TGS_TOK_CHECK(tgs, tok, "Invalid Private Message Command");

        ret = lpc_printf(tgs->lpc, "MSG %s %s", a, tok);
        TGS_RET_CHECK(tgs, ret, "lpc_printf", ret);

        ret = tui_scroll_printf(tgs->tui, "[%s] %s", a, tok);
        TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);
    }
    else if (!strcasecmp(tok, "/start"))
    {
        tok = strtok(NULL, "");
        TGS_TOK_CHECK(tgs, tok, "Invalid Start Command");

        if (tgs->game != NULL)
        {
            ret = tui_scroll_write(tgs->tui, "*** Game Already In Play");
            TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
            return 0;
        }

        tgs->game = _game_find(tok);
        if (tgs->game == NULL)
        {
            ret = tui_scroll_printf(tgs->tui, "*** Game %s Not Found", tok);
            TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);
            return 0;
        }

        ret = tgs->game->start(tgs);
        TGS_RET_CHECK(tgs, ret, "game->start", ret);

        ret = lpc_printf(tgs->lpc, "START %s", tok);
        TGS_RET_CHECK(tgs, ret, "lpc_printf", ret);
    }
    else if (!strcasecmp(tok, "/join"))
    {
        a = strtok(NULL, " \t");
        TGS_TOK_CHECK(tgs, a, "Invalid Join Command");

        tok = strtok(NULL, "");
        TGS_TOK_CHECK(tgs, tok, "Invalid Join Command");

        if (tgs->game != NULL)
        {
            ret = tui_scroll_write(tgs->tui, "*** Game Already In Play");
            TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
            return 0;
        }

        tgs->game = _game_find(a);
        if (tgs->game == NULL)
        {
            ret = tui_scroll_printf(tgs->tui, "*** Game %s Not Found", a);
            TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);
            return 0;
        }

        ret = tgs->game->start(tgs);
        TGS_RET_CHECK(tgs, ret, "game->start", ret);

        ret = lpc_printf(tgs->lpc, "JOIN %s", tok);
        TGS_RET_CHECK(tgs, ret, "lpc_printf", ret);
    }
    else if (!strcasecmp(tok, "/part"))
    {
        if (tgs->game == NULL)
        {
            ret = tui_scroll_write(tgs->tui, "*** No Game In Play");
            TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
            return 0;
        }

        ret = tgs->game->part(tgs);
        TGS_RET_CHECK(tgs, ret, "game->part", ret);

        ret = lpc_printf(tgs->lpc, "PART");
        TGS_RET_CHECK(tgs, ret, "lpc_printf", ret);
    }
    else if (!strcasecmp(tok, "/help"))
    {
        ret = tui_scroll_write(tgs->tui,
            "*** /msg <name> <msg> - Send private message <msg> to <name>");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);

        ret = tui_scroll_write(tgs->tui,
            "*** /start <game> - Start a new instance of <game>");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);

        ret = tui_scroll_write(tgs->tui,
            "*** /join <game> <inst> - Join instance <inst> of <game>");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);

        ret = tui_scroll_write(tgs->tui, "*** /part - Leave current game");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);

        ret = tui_scroll_write(tgs->tui, "*** /quit - Exit TGS");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);

        ret = tui_scroll_write(tgs->tui, "*** Avaialable Games:");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);

        for (x = 0; _game_list[x]; x++)
        {
            ret = tui_scroll_printf(tgs->tui, "***   %s", _game_list[x]->name);
            TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
        }

        ret = tui_scroll_write(tgs->tui,
            "*** Once in a game, use <TAB> to switch between chat and game");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
    }
    else
    {
        ret = tui_scroll_write(tgs->tui, "*** Invalid User Command");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
    }

    return 0;
}

/* parse and handle server input */
int _server_parse(TGS *tgs, char *buf, int len)
{
    int   ret;
    char *tok;
    char *a;
    char *b;

    tok = strtok(buf, " \t");
    if (tok == NULL)
        return 0;

    if (!strcasecmp(tok, "MSG"))
    {
        a = strtok(NULL, " \t");
        TGS_TOK_CHECK(tgs, a, "Invalid Server Command");

        b = strtok(NULL, " \t");
        TGS_TOK_CHECK(tgs, b, "Invalid Server Command");

        tok = strtok(NULL, "");
        TGS_TOK_CHECK(tgs, tok, "Invalid Server Command");

        if (strcmp(b, "*"))
            ret = tui_scroll_printf(tgs->tui, "[*%s*] %s", a, tok);
        else
            ret = tui_scroll_printf(tgs->tui, "<%s> %s", a, tok);
        TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);
    }
    else if (!strcasecmp(tok, "NAME"))
    {
        tok = strtok(NULL, " \t");
        TGS_TOK_CHECK(tgs, tok, "Invalid Server Command");

        if (!strcasecmp(tok, "OK"))
            return 0;

        tok = strtok(NULL, "");
        TGS_TOK_CHECK(tgs, tok, "Invalid Server Command");

        ret = tui_scroll_printf(tgs->tui, "*** %s", tok);
        TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);

        tgs->name[0] = 0;
        tui_set_prompt(tgs->tui, TGS_PROMPT_NAME);
    }
    else if (!strcasecmp(tok, "STATUS"))
    {
        tok = strtok(NULL, "");
        TGS_TOK_CHECK(tgs, tok, "Invalid Server Command");

        ret = tui_scroll_printf(tgs->tui, "*** %s", tok);
        TGS_RET_CHECK(tgs, ret, "tui_scroll_printf", ret);
    }
    else if (!strcasecmp(tok, "GAME"))
    {
        tok = strtok(NULL, "");
        TGS_TOK_CHECK(tgs, tok, "Invalid Server Command");

        /* it's possible that the server sent data before it received the
           PART command, so we make sure we have a valid game pointer */
        if (tgs->game)
        {
            ret = tgs->game->data(tgs, tok);
            TGS_RET_CHECK(tgs, ret, "game->data", ret);
        }
    }
    else
    {
        ret = tui_scroll_write(tgs->tui, "*** Invalid Server Command");
        TGS_RET_CHECK(tgs, ret, "tui_scroll_write", ret);
    }

    return 0;
}

/* find a game module given a name */
static TGS_GAME *_game_find(char *name)
{
    int x;

    for (x = 0; _game_list[x]; x++)
    {
        if (!strcasecmp(_game_list[x]->name, name))
            return _game_list[x];
    }

    return NULL;
}

/* update the status line */
int _update_status(TGS *tgs, time_t cur_time, char *server, uint16_t port)
{
    int  ret;
    char buf[TGS_STR_LEN];
    int  len = 1;

    buf[0] = ' ';
    len += strftime(buf + len, TGS_STR_LEN - len, "%H:%M",
        localtime(&cur_time));

    snprintf(buf + len, TGS_STR_LEN - len, " [%s:%d] %s", server, port,
        lpc_status(tgs->lpc));

    if (strcasecmp(buf, tgs->status))
    {
        strncpy(tgs->status, buf, TGS_STR_LEN);
        tgs->status[TGS_STR_LEN - 1] = 0;

        ret = tui_set_status(tgs->tui, buf, 0);
        TGS_RET_CHECK(tgs, ret, "tui_set_status", ret);
    }

    return 0;
}

/* print usage information */
void _usage(char *name)
{
    fprintf(stderr, "usage: %s [-h] [-p <port>] [-s <server>]\n", name);
    fprintf(stderr, "    -p <port>    port to connect to on server\n");
    fprintf(stderr, "    -s <server>  name of server to connect to\n");
}
